package me.wpf.utils;

import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.elasticsearch.client.RestHighLevelClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Description: elasticsearch连接池 单例
 *
 * @author 王朋飞
 * @create 2019/11/6
 */
public class ElasticSearchPoolUtil {
    private static Logger logger = LoggerFactory.getLogger(ElasticSearchPoolUtil.class);

    private static GenericObjectPool<RestHighLevelClient> clientPool = null;
    private static ElasticSearchPoolUtil elasticSearchPoolUtil = null;

    /**
     * 集群hosts,多个节点用逗号隔开
     */
    private static String hosts = ConfigPropertiesUtil.getConfigProperties().getProperty("es.hostName");

    private ElasticSearchPoolUtil() {
    }

//    public static ElasticSearchPoolUtil getInstance() {
//        ElasticSearchPoolConfig config = new ElasticSearchPoolConfig();
//        if (elasticSearchPoolUtil == null) {
//            synchronized (ElasticSearchPoolUtil.class) {
//                if (elasticSearchPoolUtil == null) {
//                    elasticSearchPoolUtil = new ElasticSearchPoolUtil();
//                }
//            }
//        }
//        clientPool = getClientPool(hosts, config);
//        return elasticSearchPoolUtil;
//    }

//    static {
//        ElasticSearchPoolConfig config = new ElasticSearchPoolConfig();
//        getClientPool(hosts, config);
//    }


    private static GenericObjectPool<RestHighLevelClient> getClientPool(String hosts, ElasticSearchPoolConfig config) {
        if (clientPool == null) {
            synchronized (ElasticSearchPoolUtil.class) {
                if (clientPool == null) {
                    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
                    //基本功能：
                    poolConfig.setMaxIdle(config.getMaxIdle()); //池中最多可容纳的实例（instances）个数8->200,(是指pool中最多能保留多少个空闲对象)
                    poolConfig.setMinIdle(config.getMinIdle());  //池中最少需要容纳的实例（instances）个数0->1,(是指pool中最少有多少个空闲对象)
                    poolConfig.setMaxTotal(config.getMaxTotal()); //池中最多可用的实例个数8->200
                    poolConfig.setMaxWaitMillis(config.getMaxWaitMillis());//调用borrowObject方法时，当连接满时最多需要等待的最长时间-1->4000
                    //高级功能：
                    //使用时检查对象（默认不检查）
                    poolConfig.setTestOnBorrow(true);  //调用borrowObject方法时，依据此标识判断是否需要对返回的结果进行校验，如果校验失败会 删除当前实例，并尝试再次获取
                    poolConfig.setTestWhileIdle(true); //闲置实例校验标识，如果校验失败会删除当前实例
                    poolConfig.setTestOnCreate(true);  //调用borrowObject方法时，依据此标识判断是否需要对返回的结果进行校验，如果校验失败会删除当前实例，并尝试再次获取
                    poolConfig.setTestOnReturn(true);   //调用returnObject方法时，依据此标识判断是否需要对返回的结果进行校验
                    poolConfig.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");//回收策略
                    poolConfig.setBlockWhenExhausted(true); //当资源池用尽后，调用者是否要等待。只有当为true时，下面的maxWaitMillis才会生效
                    //jmx启用 之后可以实时的查看线程池对象的状态
                    poolConfig.setJmxEnabled(false);
                    poolConfig.setJmxNameBase("namebase");
                    poolConfig.setJmxNamePrefix("nameprefix");
                    //驱逐线程每次检查对象个数
                    poolConfig.setNumTestsPerEvictionRun(config.getNumTestsPerEvictionRun());//池中处于空闲状态的对象每次被检测是否回收时最多只检测3个处于空闲状态的对象,比如该值设置为3,此时池中有5个闲置对象,那么每次只会检查前三个闲置对象3->10
                    //空闲连接被驱逐前能够保留的时间,资源池中资源最小空闲时间(单位为毫秒)，达到此值后空闲资源将被移除
                    poolConfig.setMinEvictableIdleTimeMillis(config.getMinEvictableIdleTimeMillis());//池中对象处于空闲状态开始到被回收的最短时间1800000
                    //当空闲线程大于minIdle 空闲连接能够保留时间，同时指定会被上面的覆盖
                    poolConfig.setSoftMinEvictableIdleTimeMillis(config.getSoftMinEvictableIdleTimeMillis());//池中对象处于空闲状态开始到被回收的最短时间3
                    //驱逐线程执行间隔时间
                    poolConfig.setTimeBetweenEvictionRunsMillis(60 * 1000); //建议设置，空闲资源的检测周期,周期自行选择,单位为毫秒

                    poolConfig.setLifo(true);//池中实例的操作是否按照LIFO（后进先出）的原则true
                    poolConfig.setFairness(false);//租借池化对象的客户端按照FIFO进行false

                    clientPool = new GenericObjectPool<>(new EsClientPoolFactory(hosts), poolConfig);
                    logger.info("getClientPool(51)->获取一个新ES服务连接字信息,空闲数Idle[" + clientPool.getNumIdle() + "]");
                    //解决连接池泄露问题
                    AbandonedConfig abandonedConfig = new AbandonedConfig();
                    abandonedConfig.setLogAbandoned(true);
                    abandonedConfig.setUseUsageTracking(false);
                    abandonedConfig.setRemoveAbandonedOnMaintenance(true); //在Maintenance的时候检查是否有泄漏
                    abandonedConfig.setRemoveAbandonedOnBorrow(true); //borrow 的时候检查泄漏
                    abandonedConfig.setRemoveAbandonedTimeout(config.getRemoveAbandonedTimeout()); //如果一个对象borrow之后10秒还没有返还给pool，认为是泄漏的对象
                    clientPool.setAbandonedConfig(abandonedConfig);
                    clientPool.setTimeBetweenEvictionRunsMillis(config.getTimeBetweenEvictionRunsMillis());//5秒运行一次维护任务
                }
                // clientPool.isClosed ();
            }
        } else {
            int iCount = 0;
            logger.info("getClientPool(63)->获取已存在ES服务连接字信息。,创建总数[" + clientPool.listAllObjects().size() + "],空闲数Idle[" + clientPool.getNumIdle() + "],激活数Active[" + clientPool.getNumActive() + "]");
        }
        return clientPool;
    }

    /**
     * 获得对象,
     * borrowObject方法的主要流程是首先看里面的idleReferences是否为空，
     * 如果不为空，则从里面取一个对象出来并返回，否则通过factory来创建一个object
     *
     * @return
     * @throws Exception
     */
    public static RestHighLevelClient getEsClient() {
        // 从池中取一个对象
        RestHighLevelClient esClient = null;
        try {
            if (clientPool.isClosed() == false) {
                esClient = clientPool.borrowObject();
                logger.info("获取es服务连接字成功,当前活跃Id=[" + clientPool.getNumActive() + "]");
            } else {
                logger.info("获取es服务连接失败，因为已关闭了，状态为STOPPED/INACTIVE");
            }
        } catch (Exception ex) {
            logger.error("获取ES服务连接字错误:", ex);
            ex.getStackTrace();
        }
        return esClient;
    }

    /**
     * 归还对象
     *
     * @param client
     */
    public static void releaseClient(RestHighLevelClient client) {
        // 使用完毕之后，归还对象
        if (clientPool.isClosed() == false) {
            clientPool.returnObject(client);
            logger.info("ES服务连接字正在释放中...");
        } else
            logger.info("ES服务连接字已释放...");
    }


}
